package com.ruben.simplescaffold.config;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import javax.annotation.Resource;
import javax.servlet.MultipartConfigElement;
import javax.servlet.http.HttpServletRequest;

import org.apache.ibatis.reflection.property.PropertyNamer;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.ruben.simplescaffold.exception.PageException;
import com.ruben.simplescaffold.interceptor.UserInfoInterceptor;

import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;

/**
 * web配置类
 *
 * @author <achao1441470436@gmail.com>
 * @since 2021/5/18 0018 14:52
 */
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    public static final String ORDERS_PROPERTY_NAME = PropertyNamer.methodToProperty(LambdaUtils.extract((SFunction<PageDTO<?>, List<OrderItem>>) PageDTO::getOrders).getImplMethodName());
    @Resource
    private UserInfoInterceptor userInfoInterceptor;

    /**
     * 不要默认跳转error页面配置
     *
     * @return org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver
     * @author <achao1441470436@gmail.com>
     * @since 2021/5/22 0022 21:59
     */
    public ErrorViewResolver errorViewResolver() {
        return (request, status, model) -> {
            // 进行你的配置，如我这里是抛出异常然后统一处理，根据实际业务需求去做
            throw new PageException("无法解析视图" + request.getRequestURI());
        };
    }

    /**
     * 静态资源映射
     *
     * @param registry 注册器
     * @author <achao1441470436@gmail.com>
     * @since 2021/5/21 0021 15:00
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
    }

    /**
     * 文件大小限制
     *
     * @return javax.servlet.MultipartConfigElement
     * @author <achao1441470436@gmail.com>
     * @since 2021/5/22 0022 21:52
     */
    @Bean
    public MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        // 单个数据大小
        factory.setMaxFileSize(DataSize.of(100, DataUnit.MEGABYTES));
        // 总上传数据大小
        factory.setMaxRequestSize(DataSize.of(100, DataUnit.MEGABYTES));
        return factory.createMultipartConfig();
    }

    /**
     * @param converters 转换器
     * @author <achao1441470436@gmail.com>
     * @since 2021/5/21 0021 15:08
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter();
        // 设置允许ContentType
        List<MediaType> supportedMediaTypes = new ArrayList<>();
        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
        supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
        supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
        supportedMediaTypes.add(MediaType.APPLICATION_PDF);
        supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XML);
        supportedMediaTypes.add(MediaType.IMAGE_GIF);
        supportedMediaTypes.add(MediaType.IMAGE_JPEG);
        supportedMediaTypes.add(MediaType.IMAGE_PNG);
        supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM);
        supportedMediaTypes.add(MediaType.TEXT_HTML);
        supportedMediaTypes.add(MediaType.TEXT_MARKDOWN);
        supportedMediaTypes.add(MediaType.TEXT_PLAIN);
        supportedMediaTypes.add(MediaType.TEXT_XML);
        fastJsonConverter.setSupportedMediaTypes(supportedMediaTypes);
        fastJsonConverter.setFastJsonConfig(FastJsonConfigHandler.getConfig());
        converters.add(fastJsonConverter);
    }

    /**
     * Add resolvers to support custom controller method argument types.
     * <p>This does not override the built-in support for resolving handler
     * method arguments. To customize the built-in support for argument
     * resolution, configure {@link RequestMappingHandlerAdapter} directly.
     *
     * @param resolvers initially an empty list
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new HandlerMethodArgumentResolver() {

            /**
             * Whether the given {@linkplain MethodParameter method parameter} is
             * supported by this resolver.
             *
             * @param parameter the method parameter to check
             * @return {@code true} if this resolver supports the supplied parameter;
             * {@code false} otherwise
             */
            @Override
            public boolean supportsParameter(MethodParameter parameter) {
                return parameter.getParameterType() == Page.class;
            }

            /**
             * Resolves a method parameter into an argument value from a given request.
             * A {@link ModelAndViewContainer} provides access to the model for the
             * request. A {@link WebDataBinderFactory} provides a way to create
             * a {@link WebDataBinder} instance when needed for data binding and
             * type conversion purposes.
             *
             * @param parameter     the method parameter to resolve. This parameter must
             *                      have previously been passed to {@link #supportsParameter} which must
             *                      have returned {@code true}.
             * @param mavContainer  the ModelAndViewContainer for the current request
             * @param webRequest    the current request
             * @param binderFactory a factory for creating {@link WebDataBinder} instances
             * @return the resolved argument value, or {@code null} if not resolvable
             * @throws Exception in case of errors with the preparation of argument values
             */
            @Override
            public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
                //获取请求对象
                HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
                // 获取参数，并且转换为Map<Object, Object>
                Map<Object, Object> paramMap = Optional.ofNullable(request).map(HttpServletRequest::getParameterMap).map(Map::entrySet).map(Set::parallelStream).map(s -> s.collect(HashMap::new, (m, v) -> m.put(v.getKey(), Optional.ofNullable(v.getValue()).filter(ArrayUtil::isNotEmpty).map(Arrays::stream).flatMap(Stream::findFirst).orElse(null)), HashMap::putAll)).orElseGet(HashMap::new);
                // 处理orders
                Optional.ofNullable(paramMap.get(ORDERS_PROPERTY_NAME)).map(String::valueOf).map(orderString -> JSON.parseArray(orderString, OrderItem.class)).filter(CollectionUtil::isNotEmpty).ifPresent(orders -> paramMap.put(ORDERS_PROPERTY_NAME, orders));
                // 序列化然后反序列化成Page对象返回
                return JSON.parseObject(JSON.toJSONString(paramMap), Page.class);
            }
        });
    }

    /**
     * Add Spring MVC lifecycle interceptors for pre- and post-processing of
     * controller method invocations and resource handler requests.
     * Interceptors can be registered to apply to all requests or be limited
     * to a subset of URL patterns.
     *
     * @param registry 拦截器注册
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册注解拦截器，并排除不需要注解鉴权的接口地址 (与登录拦截器无关)
        registry.addInterceptor(new SaAnnotationInterceptor())
                .addPathPatterns("/**").excludePathPatterns();
        registry.addInterceptor(userInfoInterceptor).addPathPatterns("/**");
    }

}
